
#include <fstream>
#include <vector>

#include "../BSP.h"
#include "../GLRenderer.h"
#include "../GLMesh.h"
#include "../Entities.h"

#include "globals.h"

using namespace std;

const int NUM_CHUNKS = 3;
enum{
	PLANES_CHUNK,
	TEXTURES_CHUNK,
	VERTICES_CHUNK,
	VTX_INDICES_CHUNK,
	POLYS_CHUNK,
	NODES_CHUNK,
	PVS_CHUNK,
	MESH_NDX_NODES_CHUNK,
	STAIC_MESHES_CHUNK,
	ENTITIES_CHUNK,
};

//extern vector<Poly> total_polygon_list;
//extern vector<Plane> total_planes_list;

void SaveFile(){

	class ChunkPos{
	public:
		int offset;
		int length;
	};

	class Header{
	public:
		int num_leaves;
		ChunkPos chunks[10];
	}file_header;


	ofstream out_file("file.map", ios::out | ios::binary);

	//Header new_header;
	file_header.num_leaves = 0;
	for(auto p_node : nodes_array)
	{
		if(p_node->has_polys())
		{
			file_header.num_leaves++;
		}
	}

	//out_file.write((char*)&file_header, sizeof(Header));

	//for(auto& plane : total_planes_list)
	//{
	//	out_file.write((char*)&plane, sizeof(Plane));
	//}

	vector<Plane> out_planes;
	vector<unsigned short> plane_indices;
	vector<unsigned short> divplane_indices;
	for(auto p_node : nodes_array)
	{
		for(auto& plane : p_node->planes())
		{
			bool b = true;
			for(auto& plane2 : out_planes)
			{
				if(	fabs(plane2.d - plane.d) < epsilon 			&&
					fabs(plane2.n[0] - plane.n[0]) < epsilon 	&&
					fabs(plane2.n[1] - plane.n[1]) < epsilon 	&&
					fabs(plane2.n[2] - plane.n[2]) < epsilon 	)
				{
					plane_indices.push_back(addressof(plane2) - &out_planes[0]);
					b = false;
					break;
				}
			}
			if(b)
			{
				out_planes.push_back(plane);
				plane_indices.push_back(out_planes.size() - 1);
			}
		}

		auto div_plane = p_node->div_plane();

		bool b = true;
		for(auto& plane2 : out_planes)
		{
			if(	fabs(plane2.d - div_plane.d) < epsilon 			&&
				fabs(plane2.n[0] - div_plane.n[0]) < epsilon 	&&
				fabs(plane2.n[1] - div_plane.n[1]) < epsilon 	&&
				fabs(plane2.n[2] - div_plane.n[2]) < epsilon 	)
			{
				divplane_indices.push_back(addressof(plane2) - &out_planes[0]);
				b = false;
				break;
			}
		}
		if(b)
		{
			out_planes.push_back(div_plane);
			divplane_indices.push_back(out_planes.size() - 1);
		}
	}

	cout << out_planes.size() << " " << plane_indices.size() << endl;

	unsigned int total_num_polygons = 0;
	for(auto p_node : nodes_array)
	{
		total_num_polygons += p_node->polygons().size();
	}

	class OutStaticMesh{
	public:
		char name[64];
		char file_name[64];
		glm::vec4 angles;
		glm::vec3 pos;
		short node_ndx;
		short num_nodes;
	};

	vector<OutStaticMesh> out_static_meshes;
	vector<short> mesh_ndx_nodes;

	for(auto& mesh : g_static_meshes)
	{
		OutStaticMesh out_mesh;
		strcpy(out_mesh.name, mesh.name().c_str());
		strcpy(out_mesh.file_name, mesh.p_mesh()->file_name().c_str());

		glm::mat4x4 mat = mesh.model_matrix();
		//out_mesh.angles = glm::eulerAngles(glm::quat_cast(mat));
		//out_mesh.angles = glm::eulerAngles(glm::quat_cast(mat));
		glm::quat q = glm::quat_cast(mat);
		glm::vec3 axis = glm::axis(q);
		out_mesh.angles[0] = axis[0];
		out_mesh.angles[1] = axis[1];
		out_mesh.angles[2] = axis[2];
		out_mesh.angles[3] = glm::degrees(glm::angle(q));
		//glm::
		out_mesh.pos = {mat[3][0], mat[3][1], mat[3][2]};
		cout << "angles = " <<  out_mesh.angles[0] << " " << out_mesh.angles[1] << " " << out_mesh.angles[2] << endl;
		cout << "pos = " <<  out_mesh.pos[0] << " " << out_mesh.pos[1] << " " << out_mesh.pos[2] << endl;
		cout << "size = " << sizeof(OutStaticMesh) << endl;
		//system("pause");
		out_mesh.node_ndx = mesh_ndx_nodes.size();
		out_mesh.num_nodes = 0;

		for(auto p_node : nodes_array)
		{
			if(std::find(p_node->p_static_meshes().begin(), p_node->p_static_meshes().end(), &mesh) != p_node->p_static_meshes().end())
			{
				mesh_ndx_nodes.push_back(p_node->id());
				out_mesh.num_nodes++;
			}
		}

		out_static_meshes.push_back(out_mesh);

	}

	class OutPoly{
	public:
		unsigned short vertex_index;
		unsigned short num_vertices;
		unsigned short plane_index;
		unsigned short tex_id;

	};
	vector<OutPoly> out_polys;

	class OutNode{
	public:
		unsigned short poly_index;
		unsigned short num_polys;
		unsigned short div_plane_index;
		unsigned short child_nodes[2];
		float bbox_min[3];
		float bbox_max[3];
	};
	vector<OutNode> out_nodes;

	vector<char> pvs_bytes;

	string out_entities;
	for(auto& p_ent : g_entities)
	{
		p_ent->Export(out_entities);
	}

	set<shared_ptr<GLTexture>> used_textures;
	for(auto p_node : nodes_array)
	{
		p_node->GetUsedTextures(used_textures);
	}

	size_t file_offset = 0;
	file_offset += sizeof(Header);
	file_header.chunks[PLANES_CHUNK].offset = file_offset;
	file_header.chunks[PLANES_CHUNK].length = out_planes.size() * sizeof(Plane);

	file_offset += file_header.chunks[PLANES_CHUNK].length;
	file_header.chunks[TEXTURES_CHUNK].offset = file_offset;
	//file_header.chunks[TEXTURES_CHUNK].length = 64 * Renderer::texture_names.size();
	file_header.chunks[TEXTURES_CHUNK].length = 64 * used_textures.size();

	file_offset += file_header.chunks[TEXTURES_CHUNK].length;
	file_header.chunks[VERTICES_CHUNK].offset = file_offset;
	file_header.chunks[VERTICES_CHUNK].length = sizeof(float) * (3+3+2) * (BSPNode::vertices_pos_list.size()/3);

	unsigned int total_num_indices = 0;
	for(auto p_node : nodes_array)
	{
		total_num_indices += p_node->num_vtx_indices();
	}

	file_offset += file_header.chunks[VERTICES_CHUNK].length;
	file_header.chunks[VTX_INDICES_CHUNK].offset = file_offset;
	file_header.chunks[VTX_INDICES_CHUNK].length = sizeof(unsigned short) * total_num_indices;

	file_offset += file_header.chunks[VTX_INDICES_CHUNK].length;
	file_header.chunks[POLYS_CHUNK].offset = file_offset;
	file_header.chunks[POLYS_CHUNK].length = total_num_polygons * sizeof(OutPoly);

	file_offset += file_header.chunks[POLYS_CHUNK].length;
	file_header.chunks[NODES_CHUNK].offset = file_offset;
	file_header.chunks[NODES_CHUNK].length = nodes_array.size() * (sizeof(OutNode) - 2);

	file_offset += file_header.chunks[NODES_CHUNK].length;
	file_header.chunks[PVS_CHUNK].offset = file_offset;
	file_header.chunks[PVS_CHUNK].length = (file_header.num_leaves * file_header.num_leaves - file_header.num_leaves) / 16;

	if(((file_header.num_leaves * file_header.num_leaves - file_header.num_leaves) / 2) % 8 > 0)
	{
		file_header.chunks[PVS_CHUNK].length++;
	}

	file_offset += file_header.chunks[PVS_CHUNK].length;
	file_header.chunks[MESH_NDX_NODES_CHUNK].offset = file_offset;
	file_header.chunks[MESH_NDX_NODES_CHUNK].length = mesh_ndx_nodes.size() * sizeof(short);

	file_offset += file_header.chunks[MESH_NDX_NODES_CHUNK].length;
	file_header.chunks[STAIC_MESHES_CHUNK].offset = file_offset;
	file_header.chunks[STAIC_MESHES_CHUNK].length = out_static_meshes.size() * sizeof(OutStaticMesh);

	file_offset += file_header.chunks[STAIC_MESHES_CHUNK].length;
	file_header.chunks[ENTITIES_CHUNK].offset = file_offset;
	file_header.chunks[ENTITIES_CHUNK].length = out_entities.size();

	//out_file.write((char*)p_chunk, sizeof(ChunkPos) * 7);
	out_file.write((char*)&file_header, sizeof(Header));

	//PLANES_CHUNK
	out_file.write((char*)&out_planes[0], out_planes.size() * sizeof(Plane));

	//TEXTURES_CHUNK
	for(auto tex : used_textures)
	{
		string tex_name = Renderer::GetTextureName(tex);
		char name[64];
		strcpy(name, tex_name.c_str());
		out_file.write(name, 64);
	}
	/*for(auto& tex_name : Renderer::texture_names)
	{
		char name[64];
		strcpy(name, tex_name.c_str());
		out_file.write(name, 64);
	}*/

	//VERTICES_CHUNK
	for(unsigned int i = 0; i < BSPNode::vertices_pos_list.size()/3; i++)
	{
		out_file.write((char*)&BSPNode::vertices_pos_list[i*3], 3 * sizeof(float));
		out_file.write((char*)&BSPNode::vertices_uv_list[i*2], 2 * sizeof(float));
		out_file.write((char*)&BSPNode::vertices_normal_list[i*3], 3 * sizeof(float));
	}

	//VTX_INDICES_CHUNK
	unsigned short vertex_offset = 0;
	unsigned int poly_ndx = 0;
	for(auto& p_node : nodes_array)
	{
		OutNode node;
		if(!p_node->polygons().empty())
		{
			node.poly_index = poly_ndx;
		} else
		{
			node.poly_index = 0;
		}
		node.num_polys = p_node->polygons().size();

		node.div_plane_index = divplane_indices[&p_node - &nodes_array[0]];

		if(p_node->front_node())
		{
			node.child_nodes[0] = p_node->front_node()->id();
		} else
		{
			node.child_nodes[0] = 0;
		}

		if(p_node->back_node())
		{
			node.child_nodes[1] = p_node->back_node()->id();
		} else
		{
			node.child_nodes[1] = 0;
		}

		if(p_node->has_polys())
		{
			for(auto& p_node2 : nodes_array)
			{
				if(p_node == p_node2)
				{
					pvs_bytes.push_back(1);
					continue;
				}
				if(p_node2->has_polys())
				{
					pvs_bytes.push_back(p_node->IsInPvs(p_node2));
				}
			}
		}

		float *min = p_node->bbox_min(), *max = p_node->bbox_max();
		copy(min, min + 3, &node.bbox_min[0]);
		copy(max, max + 3, &node.bbox_max[0]);

		out_nodes.push_back(node);

		for(auto& poly : p_node->polygons())
		{
			OutPoly out_poly;
			out_poly.vertex_index = vertex_offset;
			unsigned short num_verts = poly.vtx_indices.size();
			out_poly.num_vertices = num_verts;
			out_poly.plane_index = plane_indices[poly_ndx];
			out_poly.tex_id = distance(used_textures.begin(), used_textures.find(poly.tex));

			out_file.write((char*)&poly.vtx_indices[0], num_verts * sizeof(unsigned short));
			vertex_offset += num_verts;
			out_polys.push_back(out_poly);

			poly_ndx++;
		}

	}

	//POLYS_CHUNK
	out_file.write((char*)&out_polys[0], out_polys.size() * sizeof(OutPoly));

	//NODES_CHUNK
	for(auto& node : out_nodes)
	{
		out_file.write((char*)&node.poly_index, sizeof(short));
		out_file.write((char*)&node.num_polys, sizeof(short));
		out_file.write((char*)&node.div_plane_index, sizeof(short));
		out_file.write((char*)&node.child_nodes[0], 2 * sizeof(short));
		out_file.write((char*)&node.bbox_min[0], sizeof(float) * 3);
		out_file.write((char*)&node.bbox_max[0], sizeof(float) * 3);
	}
	//out_file.write((char*)&out_nodes[0], out_nodes.size() * sizeof(OutNode));

	cout << sizeof(OutNode) << endl;
	cout << out_nodes[0].poly_index << " " << out_nodes[0].num_polys << " " << out_nodes[0].div_plane_index << " " << out_nodes[0].child_nodes[0] << endl;
	cout << out_nodes[1].poly_index << " " << out_nodes[1].num_polys << " " << out_nodes[1].div_plane_index << " " << out_nodes[1].child_nodes[0] << endl;

	//PVS_CHUNK

	vector<unsigned char> out_pvs_bytes;
	for(int i = 0; i < file_header.num_leaves; i++)
	{
		for(int j = i + 1; j < file_header.num_leaves; j++)
		{
			out_pvs_bytes.push_back(pvs_bytes[i*file_header.num_leaves + j]);
		}
	}

	while(out_pvs_bytes.size() % 8 != 0)
	{
		out_pvs_bytes.push_back(0);
	}



	cout << "num leaves = " << file_header.num_leaves << endl;
	int count = 0;
	for(unsigned int i = 0; i < out_pvs_bytes.size(); i += 8)
	{
		unsigned char out_byte = 0;
		out_byte |= out_pvs_bytes[i] 	<< 0;
		out_byte |= out_pvs_bytes[i+1]	<< 1;
		out_byte |= out_pvs_bytes[i+2] 	<< 2;
		out_byte |= out_pvs_bytes[i+3] 	<< 3;
		out_byte |= out_pvs_bytes[i+4]	<< 4;
		out_byte |= out_pvs_bytes[i+5] 	<< 5;
		out_byte |= out_pvs_bytes[i+6] 	<< 6;
		out_byte |= out_pvs_bytes[i+7] 	<< 7;

		out_file.write((char*)&out_byte, sizeof(unsigned char));
		cout << (out_byte & 1) << " " << ((out_byte >> 1) & 1) << " " << ((out_byte >> 2) & 1) <<
				" " << ((out_byte >> 3) & 1) << " " << ((out_byte >> 4) & 1) <<
				" " << ((out_byte >> 5) & 1) << " " << ((out_byte >> 6) & 1) <<
				" " << ((out_byte >> 7) & 1) << endl;
		count++;
	}

	cout << count << " / " << file_header.chunks[PVS_CHUNK].length << endl;

	system("pause");

	//MESH_NDX_NODES_CHUNK
	out_file.write((char*)&mesh_ndx_nodes[0], mesh_ndx_nodes.size() * sizeof(short));
	//STAIC_MESHES_CHUNK
	out_file.write((char*)&out_static_meshes[0], out_static_meshes.size() * sizeof(OutStaticMesh));
	//ENTITIES_CHUNK
	out_file.write((char*)&out_entities[0], out_entities.length());

	out_file.close();

}

void NewFunc11(){

	for(auto& mesh : g_static_meshes)
	{
		nodes_array[0]->AddMesh(&mesh);
	}

	SaveFile();
}
